package com.ruoyi.mobile.service.impl;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constant.HuaweiConstant;
import com.ruoyi.common.core.redis.RedisUtils;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.ExceptionUtil;
import com.ruoyi.common.utils.JsonUtil;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.mobile.domain.*;
import com.ruoyi.mobile.mapper.*;
import com.ruoyi.mobile.model.bo.huawei.AccessTokenBo;
import com.ruoyi.mobile.model.bo.huawei.HealthBo;
import com.ruoyi.mobile.model.bo.huawei.HuaweiAccessTokenBo;
import com.ruoyi.mobile.model.request.ReceviceDataRequest;
import com.ruoyi.mobile.model.request.huawei.QuerySampleSetRequest;
import com.ruoyi.mobile.service.IHuaWeiService;
import com.ruoyi.mobile.service.IUserOperateLogService;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;

import static java.util.Arrays.asList;


@Service
public class HuaWeiServiceImpl implements IHuaWeiService {

    private static final Logger logger = LoggerFactory.getLogger(HuaWeiServiceImpl.class);

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private IUserOperateLogService userOperateLogService;

    @Autowired
    private HuaweiHealthDetailMapper huaweiHealthDetailMapper;

    @Autowired
    private HuaweiHealthStatMapper huaweiHealthStatMapper;

    @Autowired
    private HuaweiHealthWeekStatMapper huaweiHealthWeekStatMapper;

    @Autowired
    private HuaweiHealthMonthStatMapper huaweiHealthMonthStatMapper;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private HuaweiBluetoohStatMapper huaweiBluetoohStatMapper;

    @Autowired
    private HuaweiHealthBluetoohDetailMapper huaweiHealthBluetoohDetailMapper;

    /**
     * 从缓存中获取accessToken
     * @param userId
     * @return
     */
    public String getAccessTokenFromCache(int userId, int type) {
        //从redis中获取accessToken
        AccessTokenBo accessTokenBo = JsonUtil.parseObject(JsonUtil.toJSONString(redisUtils.get(CacheConstants.HUAWEI_ACCESS_TOKEN_PREFIX + userId)), AccessTokenBo.class);
        if (accessTokenBo == null) {
            //redis中没有，那就应该重新授权了
            return null;
        }
        //检查access_token是否过期
        String expire = accessTokenBo.getAccessTokenExpire();
        LocalDateTime expireTime = LocalDateTimeUtil.parse(expire, "yyyy-MM-dd HH:mm:ss");
        if (expireTime.isBefore(LocalDateTime.now())) {
            //accessToken 失效了,需要使用refreshToken刷新accessToken
            HuaweiAccessTokenBo huaweiAccessTokenBo = refreshAcessToken(accessTokenBo.getRefreshToken(), userId, type);
            //更新redis中的accessToken
            accessTokenBo = accessTokenBo.buildByHuaweiAccessTokenBo(huaweiAccessTokenBo);
            redisUtils.set(CacheConstants.HUAWEI_ACCESS_TOKEN_PREFIX + userId, accessTokenBo); //将新的accessToken,过期时间,refreshToken放入缓存
        }
        return accessTokenBo.getAccessToken();

    }

    /**
     * 刷新华为 accessToken
     * @param refreshToken
     * @return
     */
    private HuaweiAccessTokenBo refreshAcessToken(String refreshToken, int userId, int type) {
        OkHttpClient okHttpClient = new OkHttpClient();

        RequestBody body = new FormBody.Builder()
                .add("grant_type", "refresh_token")
                .add("refresh_token", refreshToken)
                .add("client_id", HuaweiConstant.CLIENT_ID)
                .add("client_secret", HuaweiConstant.CLIENT_SECRET)
                .add("access_type", "offline")
                .build();
        Request request = new Request.Builder()
                .url(HuaweiConstant.TOKEN_URL)
                .post(body)
                .build();
        try {
            Response response = okHttpClient.newCall(request).execute();
            if (response.isSuccessful()) { // 判断是否成功
                String string = response.body().string();
                logger.info("用户id: " + String.valueOf(userId) + "   huawei refreshAccessToken response: " + string);
                HuaweiAccessTokenBo huaweiAccessTokenBo = JsonUtil.parseObject(string, HuaweiAccessTokenBo.class);
                if (huaweiAccessTokenBo == null) {
                    //记录执行日志
                    UserOperateLog userOperateLog = new UserOperateLog();
                    userOperateLog.setUserId(userId);
                    userOperateLog.setType(type);
                    userOperateLog.setStatus("1");
                    String errorMsg = StringUtils.substring(string, 0, 2000);
                    userOperateLog.setExceptionInfo(errorMsg);
                    userOperateLog.setCreateTime(DateUtils.getNowDate());
                    userOperateLogService.insertUserOperateLog(userOperateLog);

                    throw new ServiceException("刷新华为 accessToken 失败");
                }
                //更新accessToken,refreshToken和accessToken的过期时间
                return huaweiAccessTokenBo;
            } else {
                String string = response.body().string();
                logger.error("用户id: " + String.valueOf(userId) + "   huawei refreshAccessToken http status: " + response.code() + "  " + string);
                //记录执行日志
                UserOperateLog userOperateLog = new UserOperateLog();
                userOperateLog.setUserId(userId);
                userOperateLog.setType(type);
                userOperateLog.setStatus("1");
                String errorMsg = StringUtils.substring(string, 0, 2000);
                userOperateLog.setExceptionInfo(errorMsg);
                userOperateLog.setCreateTime(DateUtils.getNowDate());
                userOperateLogService.insertUserOperateLog(userOperateLog);

                throw new ServiceException("刷新华为 accessToken 失败");
            }
        } catch (ServiceException e) {
            logger.error("用户id: " + String.valueOf(userId) + "   huawei refreshAccessToken exception: ", e);
            throw new ServiceException("刷新华为 accessToken 失败");
        } catch (Exception e) {
            logger.error("用户id: " + String.valueOf(userId) + "   huawei refreshAccessToken exception: ", e);
            //记录执行日志
            UserOperateLog userOperateLog = new UserOperateLog();
            userOperateLog.setUserId(userId);
            userOperateLog.setType(type);
            userOperateLog.setStatus("1");
            String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000);
            userOperateLog.setExceptionInfo(errorMsg);
            userOperateLog.setCreateTime(DateUtils.getNowDate());
            userOperateLogService.insertUserOperateLog(userOperateLog);

            throw new ServiceException("刷新华为 accessToken 失败");
        }
    }

    /*********************************************** 获取某一段时间内的详细数据，并存入数据库 start *********************************************************/

    /**
     * 获取用户某一段时间内的华为健康数据详情，并存入数据库
     * @param userId
     */
    public boolean saveDaySampleSet(int userId, LocalDateTime startTime, LocalDateTime endTime) {
        String accessToken = getAccessTokenFromCache(userId, 8);
        if (StrUtil.isBlank(accessToken)) {
            return false;
        }
        OkHttpClient okHttpClient = new OkHttpClient();
        //构建请求内容
        QuerySampleSetRequest querySampleSetRequest = buildQuerySampleSetRequest(startTime, endTime);
        //构建请求body
        String jsonStr = JsonUtil.toJSONString(querySampleSetRequest);
        RequestBody body = RequestBody.create(MediaType.get("application/json; charset=utf-8"), jsonStr);
        //构建header
        Headers headers = buildHeaders(accessToken);
        //构建请求
        Request request = new Request.Builder()
                .url(HuaweiConstant.QUERY_SAMPLESET_URL)
                .post(body)
                .headers(headers)
                .build();
        try {
            Response response = okHttpClient.newCall(request).execute();
            if (response.isSuccessful()) { // 判断是否成功
                String string = response.body().string();
                logger.info("getuserinfo response: " + string);
                //将数据进行转换
                HealthBo healthBo = JsonUtil.parseObject(string, HealthBo.class);
                //处理数据
                handleDetailHealthData(userId, healthBo);
            } else {
                logger.error("采样数据明细查询失败: code : " + response.code() + " || message : " + response.message());
            }
        } catch (Exception e) {
            logger.error("采样数据明细查询失败: " + e.getMessage());
        }
        return true;
    }

    /**
     * 构建华为采样数据明细查询 接口的请求参数
     * @param start
     * @param end
     * @return
     */
    private QuerySampleSetRequest buildQuerySampleSetRequest(LocalDateTime start, LocalDateTime end) {
        QuerySampleSetRequest querySampleSetRequest = new QuerySampleSetRequest();
        long startTime = LocalDateTimeUtil.beginOfDay(start).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
        long endTime = LocalDateTimeUtil.endOfDay(end).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
        querySampleSetRequest.setStartTime(startTime);
        querySampleSetRequest.setEndTime(endTime);

        List<QuerySampleSetRequest.PolymerizeWithBean> polymerizeWithBeans = new ArrayList<>();
        QuerySampleSetRequest.PolymerizeWithBean polymerizeWithBean = new QuerySampleSetRequest.PolymerizeWithBean();
        polymerizeWithBean.setDataTypeName("com.huawei.continuous.steps.delta");
        polymerizeWithBeans.add(polymerizeWithBean);

        querySampleSetRequest.setPolymerizeWith(polymerizeWithBeans);
        return querySampleSetRequest;
    }

    /**
     * 构建请求header
     * @param accessToken
     * @return
     */
    private Headers buildHeaders(String accessToken) {
        Headers.Builder builder = new Headers.Builder();
        builder.add("Content-type", "application/json; charset=UTF-8");
        builder.add("Authorization", "Bearer " + accessToken);
        builder.add("x-client-id", HuaweiConstant.CLIENT_ID);
        builder.add("x-version","1.00");
        Headers headers = builder.build();
        return headers;
    }

    /**
     * 处理某一天的采样数据明细
     * @param userId
     * @param healthBo
     */
    private void handleDetailHealthData(int userId, HealthBo healthBo) {
        ZoneId zone = ZoneId.systemDefault();
        List<HuaweiHealthDetail> huaweiHealthDetails = new ArrayList<HuaweiHealthDetail>();
        for (HealthBo.GroupBean groupBean : healthBo.getGroup()) {
            if (groupBean.getSampleSet().get(0).getSamplePoints().isEmpty()) {
                //没有数据
                continue;
            }
            List<HealthBo.GroupBean.SampleSetBean.SamplePointsBean> samplePointsBeans = groupBean.getSampleSet().get(0).getSamplePoints();
            for (HealthBo.GroupBean.SampleSetBean.SamplePointsBean samplePointsBean : samplePointsBeans) {
                long endTimeLong = samplePointsBean.getEndTime();
                long startTimeLong = samplePointsBean.getStartTime();
                Instant endInstant = Instant.ofEpochMilli(endTimeLong/1000000);
                Instant startInstant = Instant.ofEpochMilli(startTimeLong/1000000);
                LocalDateTime endTime = LocalDateTime.ofInstant(endInstant, zone);
                LocalDateTime startTime = LocalDateTime.ofInstant(startInstant, zone);

                List<HealthBo.GroupBean.SampleSetBean.SamplePointsBean.ValueBean> valueBeans = samplePointsBean.getValue();
                for (HealthBo.GroupBean.SampleSetBean.SamplePointsBean.ValueBean valueBean : valueBeans) {
                    String fieldName = valueBean.getFieldName();
                    HuaweiHealthDetail huaweiHealthDetail = new HuaweiHealthDetail();
                    if ("steps_delta".equals(fieldName)) {
                        huaweiHealthDetail.setType(1);
                    } else {
                        continue;
                    }
                    huaweiHealthDetail.setUserId(userId);
                    huaweiHealthDetail.setStartTime(startTime);
                    huaweiHealthDetail.setEndTime(endTime);
                    huaweiHealthDetail.setCreateTime(new Date());
                    huaweiHealthDetail.setValue(valueBean.getFloatValue() != null ? new BigDecimal(Float.toString(valueBean.getFloatValue())) : (valueBean.getIntegerValue() != null ?  new BigDecimal(valueBean.getIntegerValue()) : new BigDecimal("0")));

                    huaweiHealthDetails.add(huaweiHealthDetail);
                }
            }
        }

        if (!huaweiHealthDetails.isEmpty()) {
            //插入数据库
            huaweiHealthDetailMapper.insertList(huaweiHealthDetails);
        }
    }

    /*********************************************** 获取某一段时间内的详细数据，并存入数据库 end *********************************************************/




    /*********************************************** 获取某一天的统计数据，并存入数据库 start *********************************************************/

    /**
     * 获取用户某一天的华为健康数据统计结果，并存入redis中
     * @param userId
     * @param localDateTime
     * @return
     */
    public boolean saveDailyPolymerize(int userId, LocalDateTime localDateTime) {
        String accessToken = getAccessTokenFromCache(userId, 3);
        if (StrUtil.isBlank(accessToken)) {
            return false;
        }
        OkHttpClient okHttpClient = new OkHttpClient();
        //构建请求参数
        HashMap<String, Object> hashMap = buildDailyPolymerizeRequest(localDateTime);
        //构建请求body
        String jsonStr = JsonUtil.toJSONString(hashMap);
        RequestBody body = RequestBody.create(MediaType.get("application/json; charset=utf-8"), jsonStr);
        //构建header
        Headers headers = buildHeaders(accessToken);
        Request request = new Request.Builder()
                .url(HuaweiConstant.DAILY_POLYMERIZE_URL)
                .post(body)
                .headers(headers)
                .build();
        try {
            Response response = okHttpClient.newCall(request).execute();
            if (response.isSuccessful()) { // 判断是否成功
                String string = response.body().string();
                logger.info("用户id: " + String.valueOf(userId) + "  获取 " + localDateTime.toLocalDate().toString() + "的华为健康数据 response: " + string);
                //将数据进行转换
                HealthBo healthBo = JsonUtil.parseObject(string, HealthBo.class);
                //处理数据
                handleDailyPolymerizeData(userId, localDateTime, healthBo);
                //记录执行日志
                UserOperateLog userOperateLog = new UserOperateLog();
                userOperateLog.setUserId(userId);
                userOperateLog.setType(3);
                userOperateLog.setStatus("0");
                userOperateLog.setCreateTime(DateUtils.getNowDate());
                userOperateLogService.insertUserOperateLog(userOperateLog);
            } else {
                String string = response.body().string();
                logger.error("用户id: " + String.valueOf(userId) + "  获取" + localDateTime.toLocalDate().toString() + "采样数据统计查询失败: code : " + response.code() + " || message : " + string);
                //记录执行日志
                UserOperateLog userOperateLog = new UserOperateLog();
                userOperateLog.setUserId(userId);
                userOperateLog.setType(3);
                userOperateLog.setStatus("1");
                String errorMsg = StringUtils.substring(string, 0, 2000);
                userOperateLog.setExceptionInfo(errorMsg);
                userOperateLog.setCreateTime(DateUtils.getNowDate());
                userOperateLogService.insertUserOperateLog(userOperateLog);
            }
        } catch (Exception e) {
            logger.error("用户id: " + String.valueOf(userId) + "  获取" + localDateTime.toLocalDate().toString() + "采样数据统计查询失败: ", e);
            //记录执行日志
            UserOperateLog userOperateLog = new UserOperateLog();
            userOperateLog.setUserId(userId);
            userOperateLog.setType(3);
            userOperateLog.setStatus("1");
            String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000);
            userOperateLog.setExceptionInfo(errorMsg);
            userOperateLog.setCreateTime(DateUtils.getNowDate());
            userOperateLogService.insertUserOperateLog(userOperateLog);
        }
        return true;
    }

    /**
     * 构建华为采样数据统计查询 接口的请求参数
     * @param localDateTime
     * @return
     */
    private HashMap<String, Object> buildDailyPolymerizeRequest(LocalDateTime localDateTime) {
        String startDay = localDateTime.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        //构建请求参数
        List<String> dataTypes = new ArrayList<String>();
        dataTypes.add("com.huawei.continuous.steps.delta");     //步数
        dataTypes.add("com.huawei.continuous.distance.delta");  //距离
        dataTypes.add("com.huawei.continuous.calories.burnt");  //卡路里
        HashMap<String, Object> hashMap = new HashMap<String, Object>();
        hashMap.put("startDay", startDay);
        hashMap.put("endDay", startDay);
        hashMap.put("timeZone","+0800");
        hashMap.put("dataTypes", dataTypes);
        return hashMap;
    }

    /**
     * 处理某一天的采样数据统计结果
     * @param userId
     * @param healthBo
     */
    private void handleDailyPolymerizeData(int userId, LocalDateTime localDateTime, HealthBo healthBo) {
        if (healthBo == null) {
            return;
        }
        HuaweiHealthStat huaweiHealthStat = new HuaweiHealthStat();
        huaweiHealthStat.setUserId(userId);

        HealthBo.GroupBean groupBean = healthBo.getGroup().get(0);
        List<HealthBo.GroupBean.SampleSetBean> sampleSetBeans = groupBean.getSampleSet();

        huaweiHealthStat.setStatDate(localDateTime.toLocalDate().atStartOfDay());

        for (HealthBo.GroupBean.SampleSetBean sampleSetBean : sampleSetBeans) {
            List<HealthBo.GroupBean.SampleSetBean.SamplePointsBean> samplePointsBeans = sampleSetBean.getSamplePoints();
            for (HealthBo.GroupBean.SampleSetBean.SamplePointsBean samplePointsBean : samplePointsBeans) {
                List<HealthBo.GroupBean.SampleSetBean.SamplePointsBean.ValueBean> valueBeans = samplePointsBean.getValue();
                for (HealthBo.GroupBean.SampleSetBean.SamplePointsBean.ValueBean valueBean : valueBeans) {
                    String fieldName = valueBean.getFieldName();
                    BigDecimal fieldValue = valueBean.getFloatValue() != null ? new BigDecimal(Float.toString(valueBean.getFloatValue())) : (valueBean.getIntegerValue() != null ?  new BigDecimal(valueBean.getIntegerValue()) : new BigDecimal("0"));
                    if ("steps".equals(fieldName)) {
                        huaweiHealthStat.setSteps(valueBean.getIntegerValue() != null ? valueBean.getIntegerValue() : 0);
                    } else if ("distance".equals(fieldName)) {
                        huaweiHealthStat.setDistance(fieldValue);
                    } else if ("calories_total".equals(fieldName)) {
                        huaweiHealthStat.setCalories(fieldValue);
                    }
                }
            }
        }
        //删除一条数据
        huaweiHealthStatMapper.delByUserIdAndDate(huaweiHealthStat.getUserId(), huaweiHealthStat.getStatDate());
        //将数据存入数据库
        huaweiHealthStatMapper.insertHuaweiHealthStat(huaweiHealthStat);
    }

    /*********************************************** 获取某一天的统计数据，并存入数据库 end *********************************************************/
    /**
     * 统计每周数据
     * @param userId
     * @param today
     */
    public void saveStatWeekData(int userId, LocalDate today) {
        //获取用户的手术时间
        User user = userMapper.selectUserById(userId);
        LocalDateTime firstBindTime = user.getFirstBindTime(); //首次绑定手环时间
        if (firstBindTime == null) {
            logger.info("用户id: " + String.valueOf(userId) + "   不统计手环周统计数据");
            return;
        }
        //检查是否到了周统计时间
        if (!checkIsTimeToStat(1, firstBindTime.toLocalDate(), today)) {
            return;
        }
        LocalDate endDate =  LocalDateTimeUtil.offset(today.atStartOfDay(),-1, ChronoUnit.DAYS).toLocalDate();  //昨天
        LocalDate startDate = LocalDateTimeUtil.offset(endDate.atStartOfDay(),-6, ChronoUnit.DAYS).toLocalDate(); //昨天的6天前，加上昨天正好7天

        Integer week = getWeekOrMonth(1, firstBindTime.toLocalDate(), today);
        //进行每周统计
        List<HuaweiHealthStat> list = huaweiHealthStatMapper.selectHuaweiHealthStatListByUserId(userId, startDate, endDate);
        //总步数
        Integer totalStep = 0;
        Integer stepDay = 0;
        //总卡路里
        BigDecimal totalCalories = new BigDecimal("0");
        Integer caloriesDay = 0;
        if (list != null && !list.isEmpty()) {
            for (HuaweiHealthStat huaweiHealthStat : list) {
                totalStep = huaweiHealthStat.getSteps() != null ? totalStep + huaweiHealthStat.getSteps() : totalStep;
                if (huaweiHealthStat.getSteps() != null && huaweiHealthStat.getSteps() > 0) {
                    stepDay += 1;
                }
                totalCalories = huaweiHealthStat.getCalories() != null ? totalCalories.add(huaweiHealthStat.getCalories()) : totalCalories;
                if (huaweiHealthStat.getCalories() != null && huaweiHealthStat.getCalories().compareTo(BigDecimal.ZERO) > 0) {
                    caloriesDay += 1;
                }
            }
        }
        HuaweiHealthWeekStat huaweiHealthWeekStat = new HuaweiHealthWeekStat();
        huaweiHealthWeekStat.setUserId(userId);
        huaweiHealthWeekStat.setStatStartDate(startDate);
        huaweiHealthWeekStat.setStatEndDate(endDate);
        huaweiHealthWeekStat.setWeek(week);
        //平均步数
        BigDecimal meanStep = 0 == stepDay ? new BigDecimal("0") : (new BigDecimal(totalStep)).divide(BigDecimal.valueOf(stepDay),3,BigDecimal.ROUND_HALF_UP);
        huaweiHealthWeekStat.setSteps(meanStep);
        //平均卡路里
        BigDecimal meanCalories = 0 == caloriesDay ? new BigDecimal("0") :  totalCalories.divide(BigDecimal.valueOf(caloriesDay),3,BigDecimal.ROUND_HALF_UP);
        huaweiHealthWeekStat.setCalories(meanCalories);

        //删除一条数据
        huaweiHealthWeekStatMapper.delByUserIdAndDate(huaweiHealthWeekStat.getUserId(), huaweiHealthWeekStat.getStatStartDate(), huaweiHealthWeekStat.getStatEndDate());
        //插入数据库
        huaweiHealthWeekStatMapper.insertHuaweiHealthWeekStat(huaweiHealthWeekStat);

        logger.info("用户id: " + String.valueOf(userId) + "   统计手环周统计数据结束");
        //记录执行日志
        UserOperateLog userOperateLog = new UserOperateLog();
        userOperateLog.setUserId(userId);
        userOperateLog.setType(4);
        userOperateLog.setStatus("0");
        userOperateLog.setCreateTime(DateUtils.getNowDate());
        userOperateLogService.insertUserOperateLog(userOperateLog);
    }

    /**
     * 统计每月数据
     * @param userId
     * @param today
     */
    public void saveStatMonthData(int userId, LocalDate today) {
        //获取用户的手术时间
        User user = userMapper.selectUserById(userId);
        LocalDateTime firstBindTime = user.getFirstBindTime(); //首次绑定手环时间
        if (firstBindTime == null) {
            logger.info("用户id: " + String.valueOf(userId) + "   不统计手环月统计数据");
            return;
        }
        //检查是否到了月统计时间
        if (!checkIsTimeToStat(2, firstBindTime.toLocalDate(), today)) {
            return;
        }
        LocalDate endDate =  LocalDateTimeUtil.offset(today.atStartOfDay(),-1, ChronoUnit.DAYS).toLocalDate(); //昨天
        LocalDate startDate = endDate.atStartOfDay().minusMonths(1).toLocalDate();  //昨天的一个月前
        Integer month = getWeekOrMonth(2, firstBindTime.toLocalDate(), today);

        //进行每月统计
        List<HuaweiHealthStat> list = huaweiHealthStatMapper.selectHuaweiHealthStatListByUserId(userId, startDate, endDate);
        //总步数
        Integer totalStep = 0;
        Integer stepDay = 0;
        //总卡路里
        BigDecimal totalCalories = new BigDecimal("0");
        Integer caloriesDay = 0;
        if (list != null && !list.isEmpty()) {
            for (HuaweiHealthStat huaweiHealthStat : list) {
                totalStep = huaweiHealthStat.getSteps() != null ? totalStep + huaweiHealthStat.getSteps() : totalStep;
                if (huaweiHealthStat.getSteps() != null && huaweiHealthStat.getSteps() > 0) {
                    stepDay += 1;
                }
                totalCalories = huaweiHealthStat.getCalories() != null ? totalCalories.add(huaweiHealthStat.getCalories()) : totalCalories;
                if (huaweiHealthStat.getCalories() != null && huaweiHealthStat.getCalories().compareTo(BigDecimal.ZERO) > 0) {
                    caloriesDay += 1;
                }
            }
        }
        HuaweiHealthMonthStat huaweiHealthMonthStat = new HuaweiHealthMonthStat();
        huaweiHealthMonthStat.setUserId(userId);
        huaweiHealthMonthStat.setStatStartDate(startDate);
        huaweiHealthMonthStat.setStatEndDate(endDate);
        huaweiHealthMonthStat.setMonth(month);
        //平均步数
        BigDecimal meanStep = 0 == stepDay ? new BigDecimal("0") : (new BigDecimal(totalStep)).divide(BigDecimal.valueOf(stepDay),3,BigDecimal.ROUND_HALF_UP);
        huaweiHealthMonthStat.setSteps(meanStep);
        //平均卡路里
        BigDecimal meanCalories = 0 == caloriesDay ? new BigDecimal("0") :  totalCalories.divide(BigDecimal.valueOf(caloriesDay),3,BigDecimal.ROUND_HALF_UP);
        huaweiHealthMonthStat.setCalories(meanCalories);

        //删除一条数据
        huaweiHealthMonthStatMapper.delByUserIdAndDate(huaweiHealthMonthStat.getUserId(), huaweiHealthMonthStat.getStatStartDate(), huaweiHealthMonthStat.getStatEndDate());
        //插入数据库
        huaweiHealthMonthStatMapper.insertHuaweiHealthMonthStat(huaweiHealthMonthStat);

        logger.info("用户id: " + String.valueOf(userId) + "   统计手环月统计数据结束");
        //记录执行日志
        UserOperateLog userOperateLog = new UserOperateLog();
        userOperateLog.setUserId(userId);
        userOperateLog.setType(5);
        userOperateLog.setStatus("0");
        userOperateLog.setCreateTime(DateUtils.getNowDate());
        userOperateLogService.insertUserOperateLog(userOperateLog);
    }

    /**
     * 检查today的前一天是否是 周统计或者月统计时间
     * @param type  1|周统计， 2|月统计
     * @param firstBindDate  首次绑定手环时间
     * @param today
     * @return
     */
    public static boolean checkIsTimeToStat(int type, LocalDate firstBindDate, LocalDate today) {
        if (2 == type) {
            LocalDateTime yesterday = LocalDateTimeUtil.offset(today.atStartOfDay(),-1, ChronoUnit.DAYS);
            Period period = firstBindDate.until(yesterday.toLocalDate());
            int totalMonths = (int) period.toTotalMonths();
            int days = period.getDays();
            if ((totalMonths > 0 && days < 1)) {
                return true;
            } else {
                return false;
            }
        } else if (1 == type) {
            long betweenDays = LocalDateTimeUtil.between(firstBindDate.atStartOfDay(), today.atStartOfDay(), ChronoUnit.DAYS);
            if (betweenDays > 0 && betweenDays % 7 == 0) {
                return true;
            } else {
                return false;
            }
        }
        return false;
    }

    /**
     * 检查today的前一天是第几周或者第几个月
     * @param type  1|周统计， 2|月统计
     * @param firstBindDate  首次绑定手环时间
     * @param today
     * @return
     */
    public static int getWeekOrMonth(int type, LocalDate firstBindDate, LocalDate today) {
        if (2 == type) {
            LocalDateTime yesterday = LocalDateTimeUtil.offset(today.atStartOfDay(),-1, ChronoUnit.DAYS);
            Period period = firstBindDate.until(yesterday.toLocalDate());
            int totalMonths = (int) period.toTotalMonths();
            int days = period.getDays();
            if ((totalMonths > 0 && days < 1)) {
                return totalMonths;
            } else {
                return 0;
            }
        } else if (1 == type) {
            long betweenDays = LocalDateTimeUtil.between(firstBindDate.atStartOfDay(), today.atStartOfDay(), ChronoUnit.DAYS);
            if (betweenDays > 0 && betweenDays % 7 == 0) {
                return Math.toIntExact(betweenDays / 7);
            } else {
                return 0;
            }
        }
        return 0;
    }


    /**
     * 每半个小时执行一次，更新用户的华为token,防止华为refreshtoken失效
     */
    public void updateHuaweiToken() {
        Integer count = userMapper.selectUserCount();
        Integer pageSize = 50;
        Integer page = (int)Math.ceil((double)count/pageSize);
        for (int i=0; i<page; i++) {
            Integer offset = i * pageSize;
            List<Integer> userIds = userMapper.selectOldUserIdListByPage(offset, pageSize);
            for (Integer userId : userIds) {
                try {
                    getAccessTokenFromCache(userId, 6);
                } catch (Exception e) {
                    logger.error("用户id: " + String.valueOf(userId) + "   防止华为refresh token失效失败: ", e);
                }
            }
        }
    }

    /**
     * 每天凌晨12点30分执行一次，获取所有用户昨天的华为健康统计数据
     */
    public void saveAllUserHuaweiStatData() {
        Integer count = userMapper.selectUserCount();
        Integer pageSize = 50;
        Integer page = (int)Math.ceil((double)count/pageSize);

        LocalDateTime yesterday = LocalDateTimeUtil.offset(LocalDateTime.now(),-1, ChronoUnit.DAYS);
        for (int i=0; i<page; i++) {
            Integer offset = i * pageSize;
            List<Integer> userIds = userMapper.selectOldUserIdListByPage(offset, pageSize);
            for (Integer userId : userIds) {
                try {
                    saveDailyPolymerize(userId, yesterday);
                    //检查这个用户是否到了每周统计的时间，进行统计
                    saveStatWeekData(userId, LocalDateTime.now().toLocalDate());
                } catch (Exception e) {
                    System.out.println("用户id为" + userId.toString() + "获取华为数据失败");
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 每天凌晨4点执行一次，获取所有用户的华为健康周统计和月统计数据
     */
    public void saveWeekMonthStatData() {
        Integer count = userMapper.selectUserCount();
        Integer pageSize = 50;
        Integer page = (int)Math.ceil((double)count/pageSize);

        for (int i=0; i<page; i++) {
            Integer offset = i * pageSize;
            List<Integer> userIds = userMapper.selectOldUserIdListByPage(offset, pageSize);
            for (Integer userId : userIds) {
                try {
                    logger.info("用户id: " + String.valueOf(userId) + "   统计手环周统计数据 start");
                    saveStatWeekData(userId, LocalDateTime.now().toLocalDate());
                    logger.info("用户id: " + String.valueOf(userId) + "   统计手环周统计数据 end");
                    logger.info("用户id: " + String.valueOf(userId) + "   统计手环月统计数据 start");
                    saveStatMonthData(userId, LocalDateTime.now().toLocalDate());
                    logger.info("用户id: " + String.valueOf(userId) + "   统计手环月统计数据 end");
                } catch (Exception e) {
                    logger.error("用户id: " + String.valueOf(userId) + "统计数据 exception：", e);
                }
            }
        }
    }

    /**
     * 补数据
     */
    public void saveAllWeekMonthStatData() {

//        LocalDate startDate = LocalDate.of(2022, 12, 13);
//        LocalDate endDate = LocalDate.of(2023, 5, 3);
//
//        Integer count = userMapper.selectBindCount();
//        Integer pageSize = 50;
//        Integer page = (int)Math.ceil((double)count/pageSize);
//
//        for (int i=0; i<page; i++) {
//            Integer offset = i * pageSize;
//            List<Integer> userIds = userMapper.selectBindUserIdListByPage(offset, pageSize);
//            for (LocalDate date = startDate; date.isBefore(endDate); date = date.plusDays(1)) {
//                for (Integer userId : userIds) {
//                    try {
//                        logger.info("用户id: " + String.valueOf(userId) + "   统计手环周统计数据 start");
//                        saveStatWeekData(userId, date);
//                        logger.info("用户id: " + String.valueOf(userId) + "   统计手环周统计数据 end");
//                        logger.info("用户id: " + String.valueOf(userId) + "   统计手环月统计数据 start");
//                        saveStatMonthData(userId, date);
//                        logger.info("用户id: " + String.valueOf(userId) + "   统计手环月统计数据 end");
//                    } catch (Exception e) {
//                        logger.error("用户id: " + String.valueOf(userId) + "统计数据 exception：", e);
//                    }
//                }
//            }
//        }

    }

    /**
     * 每2个小时跑一次，统计蓝牙手环用户每日的统计数据
     */
    public void statBluetoothData() {
        Integer count = userMapper.selectUserCount();
        Integer pageSize = 50;
        Integer page = (int)Math.ceil((double)count/pageSize);

        for (int i=0; i<page; i++) {
            Integer offset = i * pageSize;
            List<Integer> userIds = userMapper.selectNewUserIdListByPage(offset, pageSize);
            for (Integer userId : userIds) {
                try {
                    for (int m=7; m>=0; m--) {
                        LocalDate previousDay = LocalDate.now().minusDays(m);
                        //获取这一天的开始结束时间
                        LocalDateTime startOfToday = LocalDateTime.of(previousDay, LocalTime.of(0, 0, 0));
                        LocalDateTime endOfToday = LocalDateTime.of(previousDay, LocalTime.of(23, 59, 59));

                        List<HuaweiHealthBluetoohDetail> list = huaweiHealthBluetoohDetailMapper.selectHuaweiHealthBluetoohDetailList(userId, startOfToday, endOfToday);
                        if (list == null || list.size() == 0) {
                            continue;
                        }
                        //获取用户这一天的统计数据
                        HuaweiHealthStat huaweiHealthStat = huaweiHealthStatMapper.selectHuaweiHealthStatByUserId(userId, previousDay);
                        if (huaweiHealthStat == null) {
                            huaweiHealthStat = new HuaweiHealthStat();
                            huaweiHealthStat.setUserId(userId);
                            huaweiHealthStat.setStatDate(startOfToday);
                        }
                        Integer steps = 0;
                        BigDecimal distance = new BigDecimal(0);
                        BigDecimal calories = new BigDecimal(0);
                        BigDecimal maxHeartrate = new BigDecimal(0);
                        BigDecimal minHeartrate = new BigDecimal(0);
                        BigDecimal meanHeartrate = new BigDecimal(0);
                        BigDecimal totalHeartrate = new BigDecimal(0);
                        Integer totalHeartrateCount = 0;
                        BigDecimal maxSpo2 = new BigDecimal(0);
                        BigDecimal minSpo2 = new BigDecimal(0);
                        BigDecimal meanSpo2 = new BigDecimal(0);
                        BigDecimal totalSpo2 = new BigDecimal(0);
                        Integer totalSpo2Count = 0;
                        for (HuaweiHealthBluetoohDetail huaweiHealthBluetoohDetail : list) {
                            steps += huaweiHealthBluetoohDetail.getSteps();
                            distance = distance.add(huaweiHealthBluetoohDetail.getDistance());
                            calories = calories.add(huaweiHealthBluetoohDetail.getCalories());

                            if (huaweiHealthBluetoohDetail.getHeartrate().compareTo(BigDecimal.ZERO) > 0) {
                                int result = maxHeartrate.compareTo(huaweiHealthBluetoohDetail.getHeartrate());
                                if (result < 0) {
                                    maxHeartrate = huaweiHealthBluetoohDetail.getHeartrate();
                                }
                                if (minHeartrate.equals(new BigDecimal(0))) {
                                    minHeartrate = huaweiHealthBluetoohDetail.getHeartrate();
                                } else {
                                    int result2 = minHeartrate.compareTo(huaweiHealthBluetoohDetail.getHeartrate());
                                    if (result2 > 0) {
                                        minHeartrate = huaweiHealthBluetoohDetail.getHeartrate();
                                    }
                                }
                                totalHeartrate = totalHeartrate.add(huaweiHealthBluetoohDetail.getHeartrate());
                                totalHeartrateCount += 1;
                            }


                            if (huaweiHealthBluetoohDetail.getSpo2().compareTo(BigDecimal.ZERO) > 0) {
                                int result3 = maxSpo2.compareTo(huaweiHealthBluetoohDetail.getSpo2());
                                if (result3 < 0) {
                                    maxSpo2 = huaweiHealthBluetoohDetail.getSpo2();
                                }
                                if (minSpo2.equals(new BigDecimal(0))) {
                                    minSpo2 = huaweiHealthBluetoohDetail.getSpo2();
                                } else {
                                    int result4 = minSpo2.compareTo(huaweiHealthBluetoohDetail.getSpo2());
                                    if (result4 > 0) {
                                        minSpo2 = huaweiHealthBluetoohDetail.getSpo2();
                                    }
                                }
                                totalSpo2 = totalSpo2.add(huaweiHealthBluetoohDetail.getSpo2());
                                totalSpo2Count += 1;
                            }
                        }
                        if (totalHeartrateCount > 0) {
                            meanHeartrate = totalHeartrate.divide((new BigDecimal(totalHeartrateCount)), 2, RoundingMode.HALF_UP);
                        }
                        if (totalSpo2Count > 0) {
                            meanSpo2 = totalSpo2.divide((new BigDecimal(totalSpo2Count)), 2, RoundingMode.HALF_UP);
                        }

                        huaweiHealthStat.setSteps(steps);
                        huaweiHealthStat.setDistance(distance);
                        huaweiHealthStat.setCalories(calories);
                        huaweiHealthStat.setMaxHeartrate(maxHeartrate);
                        huaweiHealthStat.setMinHeartrate(minHeartrate);
                        huaweiHealthStat.setMeanHeartrate(meanHeartrate);
                        huaweiHealthStat.setMaxSpo2(maxSpo2);
                        huaweiHealthStat.setMinSpo2(minSpo2);
                        huaweiHealthStat.setMeanSpo2(meanSpo2);
                        if (huaweiHealthStat.getId() != null && huaweiHealthStat.getId() > 0) {
                            huaweiHealthStatMapper.updateHuaweiHealthStat(huaweiHealthStat);
                        } else {
                            huaweiHealthStatMapper.insertHuaweiHealthStat(huaweiHealthStat);
                        }
                    }
                } catch (Exception e) {
                    logger.error("用户id: " + String.valueOf(userId) + "蓝牙统计数据 exception：", e);
                }
            }
        }
    }


    public void consumeReceiveBluetoothData() throws JsonProcessingException {
        String dataStr = (String) redisUtils.rPop("huawei_bluetooth_data_list");
        System.out.println(dataStr);
        if (dataStr == null || "".equals(dataStr)) {
            return;
        }

        ObjectMapper objectMapper = new ObjectMapper();
        Map<String, Object> data = objectMapper.readValue(dataStr, Map.class);
        if (data == null) {
            return;
        }
        Integer userId = 0;
        List<ReceviceDataRequest> list = new ArrayList<ReceviceDataRequest>();
        for (Map.Entry<String, Object> entry : data.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            if ("userId".equals(key)) {
                userId = (Integer) value;
            }
            if ("list".equals(key)) {
                List<LinkedHashMap> tempList = (List<LinkedHashMap>) value;
                for (LinkedHashMap linkedHashMap : tempList) {
                    ReceviceDataRequest receviceDataRequest = new ReceviceDataRequest();
                    receviceDataRequest.setTag((String) linkedHashMap.get("tag"));
                    receviceDataRequest.setLen((String) linkedHashMap.get("len"));
                    receviceDataRequest.setVal((String) linkedHashMap.get("val"));
                    list.add(receviceDataRequest);
                }
            }
        }
        handleBluetoothData(userId, list);
    }

    public void handleBluetoothData(int userId, List<ReceviceDataRequest> list) {
        User user = userMapper.selectUserById(userId);
        long baseTime = 0;
        String startTime = null;
        long timeOffset = 0;
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i).getTag().equals("03")) {
                baseTime = Long.parseLong(list.get(i).getVal(), 16);
            }

            if (list.get(i).getTag().equals("05")) {
                DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                if (timeOffset != 0) {
                    startTime = dateFormat.format(new Date((baseTime + timeOffset) * 1000));
                } else {
                    startTime = dateFormat.format(new Date(baseTime * 1000));
                }
                timeOffset = (Long.parseLong(list.get(i).getVal(), 16) + 1L) * 60;
            }
            if (list.get(i).getTag().equals("06")) {
                DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                String endTime = dateFormat.format(new Date((baseTime + timeOffset) * 1000));
                HuaweiHealthBluetoohDetail huaweiHealthBluetoohDetail = getSportData(userId, startTime, endTime, list.get(i).getVal());
                if (!"1".equals(user.getBindFlag())) {
                    user.setBindFlag("1");
                    user.setFirstBindTime(LocalDateTime.now());
                    userMapper.updateUser(user);
                }
                //检查这个时间的数据有没有插入成功
                Integer id = huaweiHealthBluetoohDetailMapper.selectIdByUserIdAndTime(userId, huaweiHealthBluetoohDetail.getStartTime(), huaweiHealthBluetoohDetail.getEndTime());
                if (id == null) {
                    huaweiHealthBluetoohDetailMapper.insertHuaweiHealthBluetoohDetail(huaweiHealthBluetoohDetail);
                } else {
                    huaweiHealthBluetoohDetail.setUpdateTime(new Date());
                    huaweiHealthBluetoohDetail.setId(id);
                    huaweiHealthBluetoohDetailMapper.updateHuaweiHealthBluetoohDetail(huaweiHealthBluetoohDetail);
                }
            }
        }
    }

    public HuaweiHealthBluetoohDetail getSportData(int userId, String startTimeString, String endTimeString, String val) {
        String type1 = String.format("%8s", Integer.toBinaryString(Integer.parseInt(val.substring(0, 2), 16))).replace(' ', '0');

        char sampleBitma = type1.charAt(7);
        int sampleBitmaVal = 0;
        char step = type1.charAt(6);
        int stepVal = 0;
        char calorie = type1.charAt(5);
        int calorieVal = 0;
        char distance = type1.charAt(4);
        int distanceVal = 0;
        char height = type1.charAt(3);
        int heightVal = 0;
        char heartRateValOld = type1.charAt(2);
        int heartRateValOldAvg = 0;
        char heartRate = type1.charAt(1);
        int heartRateVal = 0;
        int bloodOxygenVal = 0;
        int heartRateValNew = 0;
        int heartRateValNewAvg = 0;
        char bit7 = type1.charAt(0);

        if (bit7 == '1') {
            String type2 = String.format("%8s", Integer.toBinaryString(Integer.parseInt(val.substring(2, 4), 16))).replace(' ', '0');
            char bloodOxygen = type2.charAt(7);
            int offset = 4;

            if (sampleBitma == '1') {
                sampleBitmaVal = Integer.parseInt(val.substring(offset, offset + 2), 16);
                offset += 2;
            }
            if (step == '1') {
                stepVal = Integer.parseInt(val.substring(offset, offset + 4), 16);
                offset += 4;
            }
            if (calorie == '1') {
                calorieVal = Integer.parseInt(val.substring(offset, offset + 4), 16);
                offset += 4;
            }
            if (distance == '1') {
                distanceVal = Integer.parseInt(val.substring(offset, offset + 4), 16);
                offset += 4;
            }
            if (height == '1') {
                heightVal = Integer.parseInt(val.substring(offset, offset + 4), 16);
                offset += 4;
            }
            if (heartRateValOld == '1') {
                heartRateValOldAvg = Integer.parseInt(val.substring(offset, offset + 2), 16);
                offset += 2;
            }
            if (heartRate == '1') {
                heartRateVal = Integer.parseInt(val.substring(offset, offset + 2), 16);
                offset += 2;
            }
            if (bloodOxygen == '1') {
                bloodOxygenVal = Integer.parseInt(val.substring(offset, offset + 2), 16);
                offset += 2;
            }
            if (heartRateValNew == '1') {
                heartRateValNewAvg = Integer.parseInt(val.substring(offset, offset + 2), 16);
                offset += 2;
            }
        } else {
            int offset = 2;
            if (sampleBitma == '1') {
                sampleBitmaVal = Integer.parseInt(val.substring(offset, offset + 2), 16);
                offset += 2;
            }
            if (step == '1') {
                stepVal = Integer.parseInt(val.substring(offset, offset + 4), 16);
                offset += 4;
            }
            if (calorie == '1') {
                calorieVal = Integer.parseInt(val.substring(offset, offset + 4), 16);
                offset += 4;
            }
            if (distance == '1') {
                distanceVal = Integer.parseInt(val.substring(offset, offset + 4), 16);
                offset += 4;
            }
            if (height == '1') {
                heightVal = Integer.parseInt(val.substring(offset, offset + 4), 16);
                offset += 4;
            }
            if (heartRateValOld == '1') {
                heartRateValOldAvg = Integer.parseInt(val.substring(offset, offset + 2), 16);
                offset += 2;
            }
            if (heartRate == '1') {
                heartRateVal = Integer.parseInt(val.substring(offset, offset + 2), 16);
                offset += 2;
            }
        }
        HuaweiHealthBluetoohDetail huaweiHealthBluetoohDetail = new HuaweiHealthBluetoohDetail();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime startTime = LocalDateTime.parse(startTimeString, formatter);
        LocalDateTime endTime = LocalDateTime.parse(endTimeString, formatter);
        huaweiHealthBluetoohDetail.setUserId(userId);
        huaweiHealthBluetoohDetail.setStartTime(startTime);
        huaweiHealthBluetoohDetail.setEndTime(endTime);
        huaweiHealthBluetoohDetail.setCreateTime(new Date());
        huaweiHealthBluetoohDetail.setSteps(stepVal);
        huaweiHealthBluetoohDetail.setCalories(BigDecimal.valueOf(calorieVal).divide(BigDecimal.valueOf(1000), 2, RoundingMode.HALF_UP));
        huaweiHealthBluetoohDetail.setDistance(BigDecimal.valueOf(distanceVal));
        huaweiHealthBluetoohDetail.setHeight(BigDecimal.valueOf(heightVal));
        huaweiHealthBluetoohDetail.setHeartrate(BigDecimal.valueOf(heartRateVal));
        huaweiHealthBluetoohDetail.setRestHeartrate(BigDecimal.valueOf(heartRateValNewAvg));
        huaweiHealthBluetoohDetail.setRestOldHeartrate(BigDecimal.valueOf(heartRateValOldAvg));
        huaweiHealthBluetoohDetail.setSpo2(BigDecimal.valueOf(bloodOxygenVal));
        return huaweiHealthBluetoohDetail;
    }


}
